import random

from DrissionPage import WebPage, ChromiumOptions
from DrissionPage.chromium_element import ChromiumElement
from DrissionPage.tools import Listener
from DrissionPage.common import ActionChains
from config.Setting import Setting


class Chrome(object):
    _page: WebPage = None

    def __init__(self, port=None):
        self.port = port or Setting.CHROME_PORT

    @property
    def page(self):
        if self._page is None:
            return self.create()
        return self._page

    def create(self) -> WebPage:
        """
        创建Chrome
        :return: WebPage
        """
        do = ChromiumOptions()
        do.set_paths(local_port=self.port)
        do.set_argument('--disable-cache')
        do.set_argument('--disable-password-generation')
        do.set_argument('--disable-ios-password-suggestions')
        do.set_argument('--no-first-run')
        do.set_argument('--start-maximized')
        do.set_pref(arg="credentials_enable_service", value='0')
        do.set_pref(arg="profile.password_manager_enabled", value='0')
        do.set_pref(arg='profile.default_content_settings.popups', value='0')
        page = WebPage('d', driver_or_options=do)
        page.set.window.maximized()
        # page.handle_alert(accept=True)
        page.close_other_tabs()

        return page

    def take(self) -> WebPage:
        """
        接管Chrome
        :return: WebPage
        """
        do = ChromiumOptions()
        do.set_paths(local_port=self.port)
        page = WebPage('d', driver_or_options=do)
        # page.set.window.maximized()
        self._page = page
        return page

    def listen_to(
            self,
            targets: str,
            next_step,
            to_dest,
            count=1,
            timeout=5
    ):
        """
        异步操作, 监听数据包
        :param targets: 设置要拦截的目标
        :param next_step: 下一个动作函数及其参数 {'func': xxx, 'args': {'xxx': xxx}}
        :param to_dest: 函数
        :param count: 数据包数量, 默认: 1
        :param timeout: 最长监听时间, 默认: 5
        :return:
        """
        listener = Listener(self.port)
        listener.show_msg = False
        listener.set_targets(targets)
        listener.listen(count=count, timeout=timeout, asyn=True)
        next_step['func'](**next_step['args'])
        for i in listener.steps():
            response = i.body
            to_dest(response)


class Action(object):
    def __init__(self, page: WebPage):
        self.page = page
        self.mouse = ActionChains(page)
        self.cur = {
            'x': random.randint(300, 500),
            'y': random.randint(300, 500)
        }

    def click(self, ele: [ChromiumElement, str]):
        """
        带有鼠标移动轨迹的点击
        :param ele: Locator对象 or str
        :return:
        """
        if isinstance(ele, str):
            ele = self.page.ele(ele)
        self.move(ele)
        self.mouse.click(ele)

    def fill(self, value: str, ele: [ChromiumElement, str]):
        """
        模拟点击之后, 再输入值
        :param value: 输入的值
        :param ele: Locator对象 or str
        :return:
        """
        if isinstance(ele, str):
            ele = self.page.ele(ele)
        self.click(ele)
        ele.input(value)

    def move(self, ele: ChromiumElement):
        """
        鼠标移动到对象, 带有移动轨迹
        :param ele: Locator对象
        :return: {x: int, y: int}
        """
        _ele = {
            'x': ele.locations.click_point[0],
            'y': ele.locations.click_point[1]
        }
        self.move_point(_ele)
        return _ele

    def move_point(self, _tar):
        """
        鼠标移动到坐标, 带有移动轨迹
        :param _tar: {x: int, y: int}
        :return:
        """
        mouse = self.mouse
        cur = self.cur
        arr_int = [-2, -1, 0, 1, 2]
        arr_float = [0.01, 0.02, 0.03]
        track_arr = self.track(_tar)
        mouse.move_to((self.cur['x'], self.cur['y']))
        for row in track_arr:
            mouse.wait(random.choice(arr_float))
            _x = cur['x'] + row[0]
            _y = cur['y'] + row[1] + random.choice(arr_int)
            mouse.move_to((_x, _y))
        if track_arr:
            self.cur['x'] = cur['x'] + track_arr[-1][0]
            self.cur['y'] = cur['y'] + track_arr[-1][1]
            mouse.move_to((self.cur['x'], self.cur['y']))

    def drag(self, ele: ChromiumElement, _tar):
        """
        组件拖动
        :param ele: Locator对象
        :param _tar: 目标坐标值 {x: int, y: int}
        :return:
        """
        mouse = self.mouse
        self.move(ele)
        mouse.hold()
        self.move_point(_tar)
        mouse.release()

    def drag_x(self, ele: ChromiumElement, distance: int):
        """
        组件横向拖动, 用于滑块
        :param ele: Locator对象
        :param distance: 滑块距离
        :return:
        """
        mouse = self.mouse
        _ele = {
            'x': ele.locations.midpoint[0],
            'y': ele.locations.midpoint[1],
        }
        self.move_point(_ele)
        mouse.hold()
        _tar = {'x': _ele['x'] + distance, 'y': _ele['y']}
        self.move_point(_tar)
        mouse.release()

    def drag_x_p2p(self, _ele, distance: int):
        """
        组件坐标点横向拖动, 用于滑块
        :param _ele: {x: int, y: int}
        :param distance: 滑块距离
        :return:
        """
        mouse = self.mouse
        self.move_point(_ele)
        mouse.hold()
        _tar = {'x': _ele['x'] + distance, 'y': _ele['y']}
        self.move_point(_tar)
        mouse.release()

    def track(self, _tar):
        """
        生成轨迹
        :param _tar: {x: int, y: int}
        :return: 轨迹
        """
        def __ease_out_expo(sep):
            if sep == 1:
                return 1
            else:
                return 1 - pow(2, -10 * sep)
        cur = self.cur
        t = []
        x_1, x_2, x_distance, type_x = self.convert(cur['x'], _tar['x'])
        y_1, y_2, y_distance, type_y = self.convert(cur['y'], _tar['y'])
        distance = max(x_distance, y_distance)
        count = 20 + int(distance / 4)
        # count = 10 + int(distance / 2)  # 滑块过不去就改用这个慢的
        _x, _y = 0, 0
        for i in range(count):
            x = round(__ease_out_expo(i / count) * x_distance)
            y = round(__ease_out_expo(i / count) * y_distance)
            if x == _x and y == _y:
                continue
            t.append([x if type_x else 0 - x, y if type_y else 0 - y])
            _x, _y = x, y
        return t

    @staticmethod
    def convert(cur_point: int, tar_point: int):
        """
        坐标点判断
        :param cur_point: 当前坐标点
        :param tar_point: 目标坐标点
        :return: 小数值坐标点, 大数值坐标点, 距离, 目标坐标点是否比当前坐标点大
        """
        max_point = max(cur_point, tar_point)
        min_point = min(cur_point, tar_point)
        distance = max_point - min_point
        type_ = True if tar_point > cur_point else False
        return min_point, max_point, distance, type_


if __name__ == '__main__':
    Chrome().create()
